package com.xiaouyudeguang.common.utils;

import com.alibaba.excel.annotation.ExcelProperty;
import com.xiaouyudeguang.common.collect.MapBuilder;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.ArrayMemberValue;
import javassist.bytecode.annotation.MemberValue;
import javassist.bytecode.annotation.StringMemberValue;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class ExcelUtils {

    public static final ClassPool pool = ClassPool.getDefault();

    public static CtClass copyClass(Class<?> originalClass) throws Exception {
        return copyClass(originalClass, MapBuilder.of());
    }

    public static CtClass copyClass(Class<?> originalClass, Map<String, String> fields) throws Exception {
        ClassClassPath classPath = new ClassClassPath(originalClass);
        pool.insertClassPath(classPath);
        ClassFile classFile = pool.get(originalClass.getName()).getClassFile();
        classFile.setName(originalClass.getName() + "_" + IdUtils.randomUUID());
        CtClass ctClass = pool.makeClass(classFile);
        return appendField(originalClass, ctClass, fields);
    }

    private static CtClass appendField(Class<?> originalClass, CtClass ctClass, Map<String, String> fields) throws Exception {
        Set<String> fieldsNames = Arrays.stream(originalClass.getDeclaredFields()).map(Field::getName).collect(Collectors.toSet());
        for (Map.Entry<String, String> entry : fields.entrySet()) {
            CtField ctField = fieldsNames.contains(entry.getKey()) ? ctClass.getDeclaredField(entry.getKey()) : new CtField(pool.get(String.class.getName()), entry.getKey(), ctClass);
            if (ctField.getAnnotation(ExcelProperty.class) == null) {
                FieldInfo fieldInfo = ctField.getFieldInfo();
                ConstPool constPool = fieldInfo.getConstPool();
                AnnotationsAttribute fieldAttr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
                Annotation fieldAnno = new Annotation(ExcelProperty.class.getName(), constPool);
                ArrayMemberValue arrayMemberValue = new ArrayMemberValue(constPool);
                arrayMemberValue.setValue(new MemberValue[]{new StringMemberValue(entry.getValue(), constPool)});
                fieldAnno.addMemberValue("value", arrayMemberValue);
                fieldAttr.addAnnotation(fieldAnno);
                fieldInfo.addAttribute(fieldAttr);
            }
            if (!fieldsNames.contains(entry.getKey())) {
                ctClass.addField(ctField);
                ctClass.addMethod(CtNewMethod.make("public String get" + StringUtils.toUpCaseFirst(entry.getKey()) + "() { return " + entry.getKey() + "; }", ctClass));
                ctClass.addMethod(CtNewMethod.make("public void set" + StringUtils.toUpCaseFirst(entry.getKey()) + "(String " + entry.getKey() + ") { this. " + entry.getKey() + " = " + entry.getKey() + "; }", ctClass));
            }
        }
        return ctClass;
    }
}
